package com.aiwiown.snackmq.cluster.routing.exchange;

import com.aiwiown.snackmq.cluster.routing.Binding;
import com.aiwiown.snackmq.cluster.routing.Exchange;
import com.aiwiown.snackmq.common.message.Message;
import com.aiwiown.snackmq.common.routing.ExchangeType;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * 【最终修复版】实现了一个基于主题（Topic）的交换器。
 * Topic Exchange 根据消息的路由键（routing key）和绑定键（binding key）之间的匹配模式来路由消息。
 * 这种匹配模式支持通配符：*（匹配一个单词）和#（匹配零个或多个单词）。
 * 该实现采用了一个高性能的、不依赖正则表达式的匹配算法。
 */
@Slf4j
public class TopicExchange implements Exchange {
    private static final long serialVersionUID = 912565489004539606L;
    @Getter
    private final String name;
    private final List<Binding> bindings = new CopyOnWriteArrayList<>();

    public TopicExchange(String name) {
        this.name = name;
    }

    @Override
    public ExchangeType getType() {
        return ExchangeType.TOPIC;
    }

    @Override
    public void addBinding(Binding binding) {
        this.bindings.add(binding);
    }

    @Override
    public void removeBinding(Binding binding) {
        this.bindings.remove(binding);
    }

    @Override
    public List<String> route(Message message) {
        final String routingKey = message.getKey();
        if (routingKey == null || routingKey.isEmpty()) {
            return Collections.emptyList();
        }

        return bindings.stream()
                .filter(b -> topicMatch(routingKey, b.getRoutingKey()))
                .map(Binding::getQueueName)
                .distinct()
                .collect(Collectors.toList());
    }

    /**
     * 一个高效的、不依赖正则表达式的 AMQP 主题匹配器。
     *
     * @param routingKey 消息的路由键，例如 "stock.usd.nyse"
     * @param bindingKey 队列绑定的键，例如 "stock.*.nyse" 或 "stock.#"
     * @return 如果路由键与绑定键匹配，则返回 true
     */
    private boolean topicMatch(final String routingKey, final String bindingKey) {
        if (routingKey == null || bindingKey == null) {
            return false;
        }
        if (routingKey.equals(bindingKey)) {
            return true;
        }

        String[] routingParts = routingKey.split("\\.");
        String[] bindingParts = bindingKey.split("\\.");

        return matchParts(routingParts, 0, bindingParts, 0);
    }

    /**
     * 【核心修复】: 这是一个功能完整的递归辅助方法，用于正确匹配路由键和绑定键的各个部分。
     *
     * @param rParts 路由键部分
     * @param rIdx   当前路由键部分的索引
     * @param bParts 绑定键部分
     * @param bIdx   当前绑定键部分的索引
     * @return 如果从当前索引开始匹配，则返回 true
     */
    private boolean matchParts(String[] rParts, int rIdx, String[] bParts, int bIdx) {
        // 循环直到其中一个键的所有部分都被处理完毕
        while (rIdx < rParts.length && bIdx < bParts.length) {
            String bPart = bParts[bIdx];

            if (bPart.equals("#")) {
                // 如果 '#' 是绑定键的最后一部分，它匹配路由键中所有剩余的部分
                if (bIdx == bParts.length - 1) {
                    return true;
                }

                // 如果 '#' 不是最后一部分，则需要进行回溯匹配
                // 尝试让 '#' 匹配 0 到 N 个单词
                for (int i = rIdx; i <= rParts.length; i++) {
                    // 递归地检查 '#' 后面的绑定部分是否能与路由键的剩余部分匹配
                    if (matchParts(rParts, i, bParts, bIdx + 1)) {
                        return true;
                    }
                }
                // 如果所有尝试都失败，则不匹配
                return false;
            }

            if (bPart.equals("*") || bPart.equals(rParts[rIdx])) {
                // 如果是 '*'，或者单词完全匹配，则两个指针都向前移动一位
                rIdx++;
                bIdx++;
            } else {
                // 任何不匹配的情况都会导致匹配失败
                return false;
            }
        }

        // 循环结束后，检查两个键是否都已完全匹配
        // 特殊情况：路由键已耗尽，但绑定键还剩一个 '#'，例如 rk="a.b", bk="a.b.#"
        if (rIdx == rParts.length && bIdx == bParts.length - 1 && bParts[bIdx].equals("#")) {
            return true;
        }

        // 只有当两个指针都到达末尾时，才算完全匹配
        return rIdx == rParts.length && bIdx == bParts.length;
    }

    @Override
    public List<Binding> getBindings() {
        // 返回一个不可修改的副本，以保证线程安全
        return Collections.unmodifiableList(new ArrayList<>(this.bindings));
    }
}